<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>vue3 diff 算法 | TBlog</title>
    <meta name="description" content="TBlog with vitePress">
    <link rel="stylesheet" href="/interview/assets/style.9156a4ed.css">
    <link rel="modulepreload" href="/interview/assets/Home.b86443e2.js">
    <link rel="modulepreload" href="/interview/assets/app.49accf37.js">
    <link rel="modulepreload" href="/interview/assets/Vue_source.md.592e192a.lean.js">
    <link rel="modulepreload" href="/interview/assets/app.49accf37.js">
    <link rel="icon" href="/favicon.ico">
    <meta name="twitter:title" content="vue3 diff 算法 | TBlog">
    <meta property="og:title" content="vue3 diff 算法 | TBlog">
  </head>
  <body>
    <div id="app"><!--[--><div class="theme"><header class="nav-bar" data-v-7161a24b><div class="sidebar-button" data-v-7161a24b><svg class="icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z" class></path></svg></div><a class="nav-bar-title" href="/interview/" aria-label="TBlog, back to home" data-v-7161a24b data-v-4a583abe><!----> TBlog</a><div class="flex-grow" data-v-7161a24b></div><div class="nav" data-v-7161a24b><nav class="nav-links" data-v-7161a24b data-v-15acbf05><!--[--><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="http://101.43.7.188:66" target="_blank" rel="noopener noreferrer" data-v-641633f9>Music <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="http://101.43.7.188:80" target="_blank" rel="noopener noreferrer" data-v-641633f9>CMC <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="https://juejin.cn/user/2163479676978734/posts" target="_blank" rel="noopener noreferrer" data-v-641633f9>掘金 <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="https://gitee.com/tu_zhiwei98" target="_blank" rel="noopener noreferrer" data-v-641633f9>Gitee <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><!--]--><!----><!----></nav></div><!--[--><!--]--></header><aside class="sidebar" data-v-6b49cdcd><nav class="nav-links nav" data-v-6b49cdcd data-v-15acbf05><!--[--><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="http://101.43.7.188:66" target="_blank" rel="noopener noreferrer" data-v-641633f9>Music <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="http://101.43.7.188:80" target="_blank" rel="noopener noreferrer" data-v-641633f9>CMC <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="https://juejin.cn/user/2163479676978734/posts" target="_blank" rel="noopener noreferrer" data-v-641633f9>掘金 <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="https://gitee.com/tu_zhiwei98" target="_blank" rel="noopener noreferrer" data-v-641633f9>Gitee <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><!--]--><!----><!----></nav><!--[--><!--]--><ul class="sidebar-links" data-v-6b49cdcd><!--[--><li class="sidebar-link"><p class="sidebar-link-item">Vue3.2X</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/">基本语法&amp;使用</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/VueX">VueX4.x</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/Router">Router4.x</a><!----></li><li class="sidebar-link"><p class="sidebar-link-item">部分源码解析</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/reactive">响应式系统</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/source/Watch">Watch</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/source/nextTick">nextTick</a><!----></li></ul></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">WebPack</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/WebPack/">基础配置</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/WebPack/optimize">优化方案</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/WebPack/common">常见的Loader以及Plugin</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/WebPack/custom">自定义Loader以及Plugin</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/WebPack/advanced">手写简单的MY_Webpack</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">Javascript</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Javascript/">基本语法</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Javascript/advanced">高级语法</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">TypeScript</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/TypeScript/">基本语法</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/TypeScript/combat">业务实战</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/TypeScript/advanced">高阶使用</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">浏览器相关</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Browser/">基本原理</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Browser/plugin">浏览器插件</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Browser/advanced">深入原理</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">计算机网络</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Network/">计算机网络</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Network/security">前端安全</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">HTML/CSS</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/CSS/">CSS</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/HTML/">HTML</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">Vite2.X</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vite/">基础配置</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vite/optimize">原理以及优化</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">Node</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Node/">基本知识</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Node/pit">踩坑记录</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">其他前端工具</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Git/">Git</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Jenkins/">Jenkins</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/npm/">包管理工具</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">踩坑记录以及经验</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Pit/">踩坑</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/solution/">一些解决方案</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">面经</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Interview/">xxx面试</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Interview/two">xxX面试</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">个人学习历程</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Myself/">个人情况说明</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Myself/pre">入行工作前</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Myself/one">工作第一年</a><!----></li></ul></li><!--]--></ul><!--[--><!--]--></aside><!-- TODO: make this button accessible --><div class="sidebar-mask"></div><main class="page" data-v-7eddb2c4><div class="container" data-v-7eddb2c4><!--[--><!--]--><div style="position:relative;" class="content" data-v-7eddb2c4><div><h2 id="vue3-diff-算法"><a class="header-anchor" href="#vue3-diff-算法" aria-hidden="true">#</a> vue3 diff 算法</h2><img src="/interview/assets/diff算法.eb2dc15a.png" style="zoom:100%;"> ## 当没有key的时候 <p>C1 是旧 节点 :C2 是新节点 会获取这两个节点的长度,然后看哪个的节点长度更小,用小的长度去遍历,然后进行 patch(更新),</p><p>如果旧节点比较长,就把多余的部分卸载</p><p>如果是新的节点比较长就,创建新的节点,然后挂载,渲染</p><p>性能比较低 <img src="/interview/assets/没有key的时候.9c52985d.png" style="zoom:100%;"></p><h3 id="有-key-的时候"><a class="header-anchor" href="#有-key-的时候" aria-hidden="true">#</a> 有 key 的时候</h3><p>一共五个步骤</p><ol><li><p>从头部开始遍历, 如果节点相同. 值得是 key 和 type 都相同.就可以认为是相同的节点,</p><p>继续遍历,直到不相同的时候,就跳出循环 <img src="/interview/assets/从头部开始遍历.b212baae.png" style="zoom:100%;"></p></li><li><p>从尾部开始遍历,继续判断类型相同不相同,进行 patch. 不是一个类型继续跳出循环 <img src="/interview/assets/从尾部遍历.4130252d.png" style="zoom:100%;"></p></li></ol><hr><p>3.如果出现了新节点,就进行添加 (mount), 找到位置, 用 null 空的进行 patch ,就是一次挂载操作 <img src="/interview/assets/新节点.01ebc655.png" style="zoom:100%;"> 4. 如果新的节点都遍历完了,还有旧节点. 就把旧节点移除 <img src="/interview/assets/移除旧节点.1292c7a7.png" style="zoom:100%;"></p><hr><img src="/interview/assets/未知节点情况.1ef2d0a6.png" style="zoom:100%;"><ol start="5"><li><p>当中间是无序的顺序</p><p>尽量在旧节点中找 可以利用的,用 key 进行索引,进行移动进行 patch,然后进行卸载 新增操作 <img src="/interview/assets/无序情况节点.653fe7f2.png" style="zoom:100%;"></p></li></ol><h3 id="key-属性"><a class="header-anchor" href="#key-属性" aria-hidden="true">#</a> key 属性:</h3><ul><li>主要用在 Vue 的虚拟 diff 算法,在新旧 Nodes 对比时辨识 Vnodes</li><li>如果不使用 key, Vue 会使用一种最大限度减少动态元素.并且尽可能的尝试修改复用相同类型元素的算法;</li><li>使用 Key,会基于 key 的变化重新排列元素顺序.并且会移除/销毁 key 不存在的元素</li></ul><h2 id="watch-watcheffect-以及副作用-的部分源码解析"><a class="header-anchor" href="#watch-watcheffect-以及副作用-的部分源码解析" aria-hidden="true">#</a> Watch, watchEffect 以及副作用 的部分源码解析</h2><h3 id="副作用-effect"><a class="header-anchor" href="#副作用-effect" aria-hidden="true">#</a> 副作用(effect)</h3><p>在 Vue 中 因为响应式的关系,Track 收集依赖处理器(), 以及 Trigger()派发更新处理器,是理解响应式的关键</p><p>​ 当我们在 vue 中 通过 Computed 等方法改变了 响应式对象,为什么 vue 总能及时的改变使用到这些响应式数据的组件进行更新以重新渲染组件?</p><p><strong>Vue 通过一个副作用（effect）函数来跟踪当前正在运行的函数。副作用是一个函数包裹器，在函数被调用前就启动跟踪，而 Vue 在派发更新时就能准确的找到这些被收集起来的副作用函数，当数据发生更新时再次执行它。</strong></p><div class="language-typescript"><pre><code><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">ReactiveEffect<span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">=</span> <span class="token builtin">any</span><span class="token operator">&gt;</span></span> <span class="token punctuation">{</span>
  <span class="token comment">//   定义了一个泛型参数，这个泛型会被当做内部副作用函数的返回值，并且这个类型本身就是一个函数</span>
  <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">;</span>
  _isEffect<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment">// 表示是否是 副作用</span>
  id<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span>
  active<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> <span class="token comment">//  表示该副作用的状态是开启还是停用</span>
  <span class="token function-variable function">raw</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token constant">T</span><span class="token punctuation">;</span> <span class="token comment">//   保存 初始值传入的函数</span>
  deps<span class="token operator">:</span> <span class="token builtin">Array</span><span class="token operator">&lt;</span>Dep<span class="token operator">&gt;</span><span class="token punctuation">;</span> <span class="token comment">//  该副作用的所有依赖</span>
  options<span class="token operator">:</span> ReactiveEffectOptions<span class="token punctuation">;</span> <span class="token comment">// 副作用对象的一些配置项</span>
  allowRecurse<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> <span class="token comment">//  而 allowRecurse 暂时不用关注，它是一个副作用函数能否自身调用的标识</span>
<span class="token punctuation">}</span>
</code></pre></div><hr><h3 id="副作用的全局变量"><a class="header-anchor" href="#副作用的全局变量" aria-hidden="true">#</a> 副作用的全局变量</h3><p>有三个变量是定义在副作用模块中的全局变量，而提前认识这些变量能够帮助我们了解整个副作用函数的生成以及调用的过程。</p><div class="language-typescript"><pre><code><span class="token keyword">type</span> <span class="token class-name">Dep</span> <span class="token operator">=</span> Set<span class="token operator">&lt;</span>ReactiveEffect<span class="token operator">&gt;</span><span class="token punctuation">;</span>
<span class="token keyword">type</span> <span class="token class-name">KeyToDepMap</span> <span class="token operator">=</span> Map<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token punctuation">,</span> Dep<span class="token operator">&gt;</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> targetMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">WeakMap<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token punctuation">,</span> KeyToDepMap<span class="token operator">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> effectStack<span class="token operator">:</span> ReactiveEffect<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> activeEffect<span class="token operator">:</span> ReactiveEffect <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span>
</code></pre></div><h3 id="targetmap："><a class="header-anchor" href="#targetmap：" aria-hidden="true">#</a> targetMap：</h3><p>targetMap 的值的类型是 KeyToDepMap，而 KeyToDepMap 又是一个以 Dep 为值的类型的 Map 对象，Dep 就是我一直在提及的依赖，Vue 收集依赖其实就是在收集 Dep 类型。所以对照 Vue2 的源码，从概念上来讲，将依赖看成是一个维护了订阅者 Set 集合的 Dep 类更容易理解，在 targetMap 中只是将 Dep 存储在一个原始的 Set 集合中，是出于减少内存开销的考虑。</p><h3 id="effectstatck"><a class="header-anchor" href="#effectstatck" aria-hidden="true">#</a> effectStatck</h3><p>这是一个存放当前正被调用的副作用的栈，当一个副作用在执行前会被压入栈中，而在结束之后会被推出栈。</p><h3 id="activeeffect"><a class="header-anchor" href="#activeeffect" aria-hidden="true">#</a> activeEffect</h3><p>这个变量标记了当前正在执行的副作用，或者也可以理解为副作用栈中的栈顶元素。当一个副作用被压入栈时，会将这个副作用赋值给 activeEffect 变量，而当副作用中的函数执行完后该副作用会出栈，并将 activeEffect 赋值为栈的下一个元素。所以当栈中只有一个元素时，执行完出栈后，activeEffect 就会为 undefined。</p><h2 id="副作用（effect）的实现"><a class="header-anchor" href="#副作用（effect）的实现" aria-hidden="true">#</a> 副作用（effect）的实现</h2><p>在学习完需要前置理解的类型与变量后，我就开始讲解副作用函数的实现，话不多说直接看代码。</p><div class="language-ts"><pre><code><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">effect</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">=</span> <span class="token builtin">any</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>
  <span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token constant">T</span><span class="token punctuation">,</span>
  options<span class="token operator">:</span> ReactiveEffectOptions <span class="token operator">=</span> <span class="token constant">EMPTY_OBJ</span>
<span class="token punctuation">)</span><span class="token operator">:</span> ReactiveEffect<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// 如果 fn 已经是一个副作用函数，则返回副作用的原始函数</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isEffect</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    fn <span class="token operator">=</span> fn<span class="token punctuation">.</span>raw<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 创建一个副作用</span>
  <span class="token keyword">const</span> effect <span class="token operator">=</span> <span class="token function">createReactiveEffect</span><span class="token punctuation">(</span>fn<span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// 如果不是延迟执行的，则立即执行一次副作用函数</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>options<span class="token punctuation">.</span>lazy<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">effect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 返回生成的副作用函数</span>
  <span class="token keyword">return</span> effect<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
复制代码<span class="token punctuation">;</span>
</code></pre></div><p>effect api 的函数相对简单，当传入的 fn 已经是一个副作用函数时，会将 fn 赋值为这个副作用的原始函数。接着会调用 <code>createReactiveEffect</code> 创建一个 <code>ReactiveEffect</code> 类型的函数，如果副作用的选项中没有设置延迟执行，那么这个副作用函数会被立即执行一次，最后将生成的副作用函数返回。</p><p>接着一起来看创建副作用函数的 createReactiveEffect 的逻辑。</p><h3 id="createreactiveeffect"><a class="header-anchor" href="#createreactiveeffect" aria-hidden="true">#</a> createReactiveEffect</h3><p>在 createReactiveEffect 中，首先会创建一个变量名为 effect 的函数表达式，之后为这个函数设置之前在 ReactiveEffect 类型中提及到的一些属性，最后将这个函数返回。</p><p>而当这个 effect 函数被执行时，会首先判断自己是不是已经停用，如果是停用状态，则会查看选项中是否有调度函数，如果有调度函数就不再处理，直接 return undefined，若是不存在调度函数，则执行并返回传入的 fn 函数，之后就不再运行下去。</p><p>如果 effect 函数状态正常，会判断当前 effect 函数是否已经在副作用栈中，若是已经被加入栈中，则不再继续处理，避免循环调用。</p><p>如果当前 effect 函数不在栈中，就会通过 cleanup 函数清理副作用函数的依赖，并且打开依赖收集开关，将副作用函数压入副作用栈中，并记录当前副作用函数为 activeEffect。这段逻辑我在介绍这两个变量时已经讲过，它就是在此处触发的。</p><p>接下来就会执行传入的 fn 函数被返回结果。</p><p>当函数执行完毕后，会将副作用函数弹出栈中，并且将依赖收集开关重置为执行副作用前的状态，再将 activeEffect 标记为当前栈顶的元素。此时一次副作用函数的执行彻底结束，跟着我一起来看一下源码的实现。</p><div class="language-ts"><pre><code><span class="token keyword">function</span> <span class="token generic-function"><span class="token function">createReactiveEffect</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">=</span> <span class="token builtin">any</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>
  <span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token constant">T</span><span class="token punctuation">,</span>
  options<span class="token operator">:</span> ReactiveEffectOptions
<span class="token punctuation">)</span><span class="token operator">:</span> ReactiveEffect<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// 通过一个函数表达式，创建一个变量名为 effect ，函数名为 reactiveEffect 的函数</span>
  <span class="token keyword">const</span> <span class="token function-variable function">effect</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token function">reactiveEffect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">unknown</span> <span class="token punctuation">{</span>
    <span class="token comment">// 如果 effect 已停用，当选项中有调度函数时返回 undefined，否则返回原始函数</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>effect<span class="token punctuation">.</span>active<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> options<span class="token punctuation">.</span>scheduler <span class="token operator">?</span> <span class="token keyword">undefined</span> <span class="token operator">:</span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>effectStack<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>effect<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// 清理依赖</span>
      <span class="token function">cleanup</span><span class="token punctuation">(</span>effect<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">// 允许收集依赖</span>
        <span class="token function">enableTracking</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        effectStack<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>effect<span class="token punctuation">)</span><span class="token punctuation">;</span>
        activeEffect <span class="token operator">=</span> effect<span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
        effectStack<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">resetTracking</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        activeEffect <span class="token operator">=</span> effectStack<span class="token punctuation">[</span>effectStack<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span> <span class="token keyword">as</span> ReactiveEffect<span class="token punctuation">;</span>
  <span class="token comment">// 为副作用函数设置属性</span>
  effect<span class="token punctuation">.</span>id <span class="token operator">=</span> uid<span class="token operator">++</span><span class="token punctuation">;</span>
  effect<span class="token punctuation">.</span>allowRecurse <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>options<span class="token punctuation">.</span>allowRecurse<span class="token punctuation">;</span>
  effect<span class="token punctuation">.</span>_isEffect <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  effect<span class="token punctuation">.</span>active <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  effect<span class="token punctuation">.</span>raw <span class="token operator">=</span> fn<span class="token punctuation">;</span>
  effect<span class="token punctuation">.</span>deps <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  effect<span class="token punctuation">.</span>options <span class="token operator">=</span> options<span class="token punctuation">;</span>
  <span class="token keyword">return</span> effect<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>当在最后一行 return 了副作用函数后，上一段提及提及当 options 参数中 lazy 为 false 时，这个副作用函数就会第一次被调用，此时就会触发这段函数 第 6 行 <code>const effect</code> 创建函数后的函数内部逻辑。</p><p>理解了 createReactiveEffect 的执行顺序后，再配合详细的逻辑讲解，相信你也已经掌握 effect 副作用函数的创建了。</p><h2 id="收集依赖、派发更新"><a class="header-anchor" href="#收集依赖、派发更新" aria-hidden="true">#</a> 收集依赖、派发更新</h2><p>为了更逻辑顺畅的引出依赖收集和派发更新的工作及实现流程，我决定在此处引入一个 Vue3 中 effect 模块的一个简单的单元测试用例，给大家讲解示例的同时顺带聊聊依赖收集和派发更新。</p><div class="language-ts"><pre><code><span class="token keyword">let</span> foo<span class="token punctuation">;</span>
<span class="token keyword">const</span> counter <span class="token operator">=</span> <span class="token function">reactive</span><span class="token punctuation">(</span><span class="token punctuation">{</span> num<span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">effect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span>foo <span class="token operator">=</span> counter<span class="token punctuation">.</span>num<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 此时 foo 应该是 0</span>
counter<span class="token punctuation">.</span>num <span class="token operator">=</span> <span class="token number">7</span><span class="token punctuation">;</span>
<span class="token comment">// 此时 foo 应该是 7</span>
复制代码<span class="token punctuation">;</span>
</code></pre></div><p>这是一个最简单的 effect 的示例，我们都知道 foo 会随着 counter.num 的改变而改变。那么究竟是如何更新的呢？</p><p>首先，counter 通过 reactive api 生成一个 proxy 代理对象。这一个生成过程在上一篇文章中已经讲解过了，所以这里就不细讲了。</p><p>接着使用 effect，向它传入一个函数。这时 effect 开始它的创建过程，在 effect 函数中会执行到下方代码的这一步。</p><div class="language-ts"><pre><code><span class="token keyword">const</span> effect <span class="token operator">=</span> <span class="token function">createReactiveEffect</span><span class="token punctuation">(</span>fn<span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span>
复制代码<span class="token punctuation">;</span>
</code></pre></div><p>通过 createReactiveEffect 开始创建 effect 函数，并返回。</p><p>当 effect 函数被返回后，就会判断当前副作用的选项中是否需要延迟执行，而这里我们没有传入任何参数，所以不是延迟加载，需要立即执行，所以会开始执行返回回来的 effect 函数。</p><div class="language-ts"><pre><code><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>options<span class="token punctuation">.</span>lazy<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token function">effect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 不需要延迟执行，执行 effect 函数</span>
<span class="token punctuation">}</span>
复制代码<span class="token punctuation">;</span>
</code></pre></div><p>于是会开始执行 createReactiveEffect 创建 effect 函数时的内部代码逻辑。</p><div class="language-ts"><pre><code><span class="token keyword">const</span> <span class="token function-variable function">effect</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token function">reactiveEffect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">unknown</span> <span class="token punctuation">{</span>
  <span class="token comment">/* 执行此函数内的逻辑 */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
复制代码<span class="token punctuation">;</span>
</code></pre></div><p>由于 effect 函数是 active 状态，并且也不在副作用栈中，于是会先清除依赖，由于现在并没有收集任何依赖，所以 cleanup 的过程不用关心。接着会将 effet 压入栈中，并设置为 activeEffect，接下来会开始执行初始传入的 fn：<code>() =&gt; (foo = counter.num)</code>。</p><p>给 foo 赋值时，会先访问 counter 的 num 属性，所以会触发 counter 的 proxy handler 的 get 陷阱：</p><div class="language-ts"><pre><code><span class="token comment">// get 陷阱</span>
<span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token function">get</span><span class="token punctuation">(</span>target<span class="token operator">:</span> Target<span class="token punctuation">,</span> key<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token builtin">symbol</span><span class="token punctuation">,</span> receiver<span class="token operator">:</span> object<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">/* 忽略逻辑 */</span>
  <span class="token comment">// 获取 Reflect 执行的 get 默认结果</span>
  <span class="token keyword">const</span> res <span class="token operator">=</span> Reflect<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> key<span class="token punctuation">,</span> receiver<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isReadonly<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 依赖收集</span>
    <span class="token function">track</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> TrackOpTypes<span class="token punctuation">.</span><span class="token constant">GET</span><span class="token punctuation">,</span> key<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> res<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
复制代码<span class="token punctuation">;</span>
</code></pre></div><p>这里我简化了 get 中的代码，只保留关键部分，可以看到在获取到 res 的值后，会通过 track 开始依赖收集。（🥺 注意要开始讲依赖收集了哦，不要走神）</p><h3 id="track-收集依赖"><a class="header-anchor" href="#track-收集依赖" aria-hidden="true">#</a> track 收集依赖</h3><p>track 函数的路径也是在 <code>@vue/reactivity</code> 库的 effect.ts 的文件中。</p><p>在 track 的过程中，首先会判断是否允许收集依赖，这个状态是受 <code>enableTracking()</code> 和 <code>pauseTracking()</code> 这一对函数控制的。接着会判断当前是否有正在执行的副作用函数，如果没有则直接 return。因为依赖收集其实就是在收集<strong>副作用函数</strong>。</p><p>接着从本文一开始介绍过的 targetMap 中去尝试获取对应的 traget 的依赖集合，并存储在 depsMap 变量中，如果获取失败，就会将当前 target 添加进依赖集合中，并将 value 初始化为 new Map()。例如在当前的示例中，target 即为 { num: 0 }，是 counter 对象的值。</p><p>在有了 depsMap 后，就会根据 target 中被读取的 key，去依赖集合中查看是否有对应 key 的依赖，并赋值给 dep。如果没有，就跟创建 depsMap 的逻辑一样，创建一个 Set 类型的集合当做值。</p><p>如果当前执行的副作用函数没有被 dep 这个 Set 集合当做依赖收集，就会将当前副作用函数添加进 dep 中，并且在当前的副作用函数的 deps 属性中添加进该依赖 dep。</p><p>看到这里，就能够想象出依赖的收集是一个什么样的结构了。以 key 为维度，将每一个 key 关联的副作用函数收集起来，存放在一个 Set 数据结构中，并以键值对的形式存储在 depsMap 的 Map 结构中。此时再看文章开头描述 targetMap 这个 Map 存储的形式 { target -&gt; key -&gt; dep } 应该说是非常明确了。</p><p>track 处理器函数的代码如下：</p><div class="language-ts"><pre><code><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">track</span><span class="token punctuation">(</span>target<span class="token operator">:</span> object<span class="token punctuation">,</span> type<span class="token operator">:</span> TrackOpTypes<span class="token punctuation">,</span> key<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// 不启用依赖收集，或者没有 activeEffect 则直接 return</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shouldTrack <span class="token operator">||</span> activeEffect <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 在 targetMap 中获取对应的 target 的依赖集合</span>
  <span class="token keyword">let</span> depsMap <span class="token operator">=</span> targetMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>target<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>depsMap<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 如果 target 不在 targetMap 中，则加入，并初始化 value 为 new Map()</span>
    targetMap<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> <span class="token punctuation">(</span>depsMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 从依赖集合中获取对应的 key 的依赖</span>
  <span class="token keyword">let</span> dep <span class="token operator">=</span> depsMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dep<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 如果 key 不存在，将这个 key 作为依赖收集起来，并初始化 value 为 new Set()</span>
    depsMap<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token punctuation">(</span>dep <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 如果依赖中并不存当前的 effect 副作用函数</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dep<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>activeEffect<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 将当前的副作用函数收集进依赖中</span>
    dep<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>activeEffect<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 并在当前副作用函数的 deps 属性中记录该依赖</span>
    activeEffect<span class="token punctuation">.</span>deps<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>dep<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
复制代码<span class="token punctuation">;</span>
</code></pre></div><p>看完 track 继续看我们的示例：</p><div class="language-ts"><pre><code><span class="token function">effect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span>foo <span class="token operator">=</span> counter<span class="token punctuation">.</span>num<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
复制代码<span class="token punctuation">;</span>
</code></pre></div><p>当 track 收集完依赖后，get 陷阱返回了 Reflect.get 的结果，读取到了 counter.num 的值为 0，并将此结果赋值给 foo 变量。此时副作用 函数第一次运行结束，foo 已经有了值：0。当副作用函数执行完，会将当前的副作用函数弹出栈中，并且将 activeEffect 赋值为 undefeind。</p><h3 id="trigger-派发更新"><a class="header-anchor" href="#trigger-派发更新" aria-hidden="true">#</a> trigger 派发更新</h3><p>搞懂了依赖收集之后，继续来看派发更新的过程。</p><p>示例的最后一行代码，将 num 赋值为 7。</p><div class="language-"><pre><code>counter.num = 7
复制代码
</code></pre></div><p>我们知道 foo 一定会同步更新为 7 的。那么过程是怎样的呢？</p><p>当对 counter.num 赋值时，会触发 set 陷阱：</p><div class="language-ts"><pre><code><span class="token keyword">const</span> result <span class="token operator">=</span> Reflect<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> receiver<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>target <span class="token operator">===</span> <span class="token function">toRaw</span><span class="token punctuation">(</span>receiver<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>hadKey<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 当 key 不存在时，触发 trigger 的 ADD 事件</span>
    <span class="token function">trigger</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> TriggerOpTypes<span class="token punctuation">.</span><span class="token constant">ADD</span><span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">hasChanged</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> oldValue<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 当 key 存在时，当新旧值变化后，触发 trigger 的 SET 事件</span>
    <span class="token function">trigger</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> TriggerOpTypes<span class="token punctuation">.</span><span class="token constant">SET</span><span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> oldValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> result<span class="token punctuation">;</span>
复制代码<span class="token punctuation">;</span>
</code></pre></div><p>一起来看 set 陷阱的部分代码，trigger 的触发会传入一个 TriggerOpTypes 的枚举，枚举有四种类型，对应增、删、改、清空操作。</p><div class="language-ts"><pre><code><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token keyword">enum</span> TriggerOpTypes <span class="token punctuation">{</span>
  <span class="token constant">SET</span> <span class="token operator">=</span> <span class="token string">&quot;set&quot;</span><span class="token punctuation">,</span>
  <span class="token constant">ADD</span> <span class="token operator">=</span> <span class="token string">&quot;add&quot;</span><span class="token punctuation">,</span>
  <span class="token constant">DELETE</span> <span class="token operator">=</span> <span class="token string">&quot;delete&quot;</span><span class="token punctuation">,</span>
  <span class="token constant">CLEAR</span> <span class="token operator">=</span> <span class="token string">&quot;clear&quot;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
复制代码<span class="token punctuation">;</span>
</code></pre></div><p>由于 counter 通过 reactive api 创建代理对象时已经添加了 num 这个 key，所以此时新旧值发生改变，就会触发 SET 事件。</p><p>接着会执行 trigger 函数。</p><p>trigger 函数会立即从 targetMap 中通过 target 获取 depsMap，如果没有对应的 depsMap 就代表当前的 traget 从未通过 track 进行依赖收集，所以直接 return，不继续执行。</p><p>接着会创建一个名为 effects 的 Set 结构的集合，它的作用是存储这个 key 所有需要派发更新执行的副作用函数。</p><p>同时声明一个 add 函数，add 函数的作用是遍历传入的副作用函数，将不是当前正在执行的 activeEffect 函数或者能够自我执行的副作用函数都加入到 effects 集合中。</p><p>然后会判断清空依赖和数组的特殊情况，按需调用 add 函数添加依赖。</p><p>之后会判断当前 key 是否不为 undefined，注意这里的判断条件 void 0，是通过 void 运算符的形式表示 undefined，如果有 key 则将 key 相关的依赖通过 add 函数添加进 effects 集合中。</p><p>随后的 Switch Case 通过区分 triggerOpTypes 来处理一些迭代键的特殊逻辑。</p><p>之后声明的 run 函数作用就是来执行添加入 effects 数组中的副作用函数。</p><p>trigger 函数的结尾就是通过 <code>effects.forEach(run)</code> 遍历集合内的所有副作用函数并执行。</p><p>先一起来看一下 trigger 的代码：</p><div class="language-ts"><pre><code><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">trigger</span><span class="token punctuation">(</span>
  target<span class="token operator">:</span> object<span class="token punctuation">,</span>
  type<span class="token operator">:</span> TriggerOpTypes<span class="token punctuation">,</span>
  key<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span>
  newValue<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span>
  oldValue<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span>
  oldTarget<span class="token operator">?</span><span class="token operator">:</span> Map<span class="token operator">&lt;</span><span class="token builtin">unknown</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">&gt;</span> <span class="token operator">|</span> Set<span class="token operator">&lt;</span><span class="token builtin">unknown</span><span class="token operator">&gt;</span>
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> depsMap <span class="token operator">=</span> targetMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>target<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>depsMap<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 该 target 从未被追踪，不继续执行</span>
    <span class="token keyword">return</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// effects 集合存放所有需要派发更新的副作用函数。</span>
  <span class="token keyword">const</span> effects <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set<span class="token operator">&lt;</span>ReactiveEffect<span class="token operator">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// 将不是当前副作用函数以及能执行自身的副作用函数加入集合中</span>
  <span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span>effectsToAdd<span class="token operator">:</span> Set<span class="token operator">&lt;</span>ReactiveEffect<span class="token operator">&gt;</span> <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>effectsToAdd<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      effectsToAdd<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span>effect<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>effect <span class="token operator">!==</span> activeEffect <span class="token operator">||</span> effect<span class="token punctuation">.</span>allowRecurse<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          effects<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>effect<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> TriggerOpTypes<span class="token punctuation">.</span><span class="token constant">CLEAR</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 当需要清除依赖时，将当前 target 的依赖全部传入</span>
    depsMap<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>add<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key <span class="token operator">===</span> <span class="token string">&quot;length&quot;</span> <span class="token operator">&amp;&amp;</span> <span class="token function">isArray</span><span class="token punctuation">(</span>target<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 处理数组的特殊情况</span>
    depsMap<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span>dep<span class="token punctuation">,</span> key<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>key <span class="token operator">===</span> <span class="token string">&quot;length&quot;</span> <span class="token operator">||</span> key <span class="token operator">&gt;=</span> <span class="token punctuation">(</span>newValue <span class="token keyword">as</span> <span class="token builtin">number</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">add</span><span class="token punctuation">(</span>dep<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token comment">// 在 SET | ADD | DELETE 的情况，添加当前 key 的依赖</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>key <span class="token operator">!==</span> <span class="token keyword">void</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">add</span><span class="token punctuation">(</span>depsMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// 对 ADD | DELETE | Map.SET 执行一些迭代键的逻辑</span>
    <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token keyword">type</span> <span class="token comment">/* 暂时忽略 */</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// 执行 effect 的函数</span>
  <span class="token keyword">const</span> <span class="token function-variable function">run</span> <span class="token operator">=</span> <span class="token punctuation">(</span>effect<span class="token operator">:</span> ReactiveEffect<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 判断是否有调度器，如果有则执行调度函数并将 effect 作为参数传入</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>effect<span class="token punctuation">.</span>options<span class="token punctuation">.</span>scheduler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      effect<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">scheduler</span><span class="token punctuation">(</span>effect<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token comment">// 否则直接执行副作用函数</span>
      <span class="token function">effect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token comment">// 遍历集合，执行收集到的副作用函数</span>
  effects<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>run<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

复制代码<span class="token punctuation">;</span>
</code></pre></div><p>在将 SwitchCase 的特殊逻辑，以及 DEV 环境的特殊逻辑隐藏后，trigger 函数的长度已经比较精简且逻辑清晰了。</p><p>回到我们的示例，当在 trigger 判断是否有 key，并将 key 对应的依赖传入 add 函数时，示例在 track 时被收集的副作用函数已经被 effects 集合获取到了。当 trigger 执行到最后一行代码时，副作用函数就会当做参数被传入 run 函数，由于没有设置调度器，所以会直接执行这个副作用函数：<code>() =&gt; (foo = counter.num)</code> ，执行完毕，foo 的值成功的被更新到 7。</p><p>至此收集依赖和派发更新的流程已经完整的结束，而本文的示例也运行完毕了，相信大家对这个过程也有了印象深刻的认识。如果还是有点犯迷糊，建议将本文 effect, track 和 trigger 的函数，以及上文 get、set 的陷阱源码联系起来再看看，相信你会豁然开朗的。</p><p>==============================================================================================================</p><h2 id="watcheffect"><a class="header-anchor" href="#watcheffect" aria-hidden="true">#</a> watchEffect</h2><p>watchEffect 用于自动收集响应式数据的依赖</p><p>当侦听到某些响应式数据变化时，我们希望执行某些操作，这个时候可以使用 watchEffect</p><p>具体案例&quot;</p><ol><li>首先，watchEffect 传入的函数会被立即执行一次，并且在执行的过程中会收集依赖；</li><li>其次，只有收集的依赖发生变化时，watchEffect 传入的函数才会再次执行；</li><li>​ 并在 unmounted() 组件卸载时 自动取消 监听 数据依赖</li></ol><div class="language-typescript"><pre><code><span class="token keyword">const</span> stop <span class="token operator">=</span> <span class="token function">watchEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">&quot;sings&quot;</span><span class="token punctuation">,</span> singer<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">会收集 依赖</span><span class="token template-punctuation string">`</span></span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">返回一个stop 函数  可以调用stop 停止监听</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">&quot;watcheffect 执行了&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>副作用 side effect</p><ul><li>​ 获取数据</li><li>​ 事件监听或订阅</li><li>​ 改变应用状态</li><li>​ 修改 DOM</li><li>​ 输出日志</li></ul><p>比如在开发中我们需要在侦听函数中执行网络请求，但是在网络请求还没有达到的时候，我们停止了侦听器，</p><p>或者侦听器侦听函数被再次执行了。</p><p>那么上一次的网络请求应该被取消掉，这个时候我们就可以清除上一次的副作用</p><p>watcheffrct 第一个参数接受一个函数 ,这个函数的也接受一个参数</p><p>第二个参数是一个对象 控制 副作用函数的执行时机</p><div class="language-typescript"><pre><code><span class="token keyword">const</span> stop <span class="token operator">=</span> <span class="token function">watchEffect</span><span class="token punctuation">(</span>
  <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">&quot;sings&quot;</span><span class="token punctuation">,</span> singer<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">&quot;watcheffect 执行了&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    <span class="token comment">// flush: &#39;post&#39;,   ==&gt;&gt;</span>
    <span class="token comment">// flush:&#39;pre&#39;       ==&gt;&gt; 挂载元素  或者更新之前执行</span>
    <span class="token comment">// flush: &#39;sync&#39;        ==&gt;&gt;  强制效果始终同步触发  ,低效的,很少需要</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><h2 id="watch"><a class="header-anchor" href="#watch" aria-hidden="true">#</a> watch</h2><p>watch 的 API 完全等同于组件 watch 选项的 Property：</p><ul><li>pwatch 需要侦听特定的数据源，并在回调函数中执行副作用；</li><li>默认情况下它是惰性的，只有当被侦听的源发生变化时才会执行回调；</li></ul><p>与 watchEffect 的比较，watch 允许我们：</p><ul><li>懒执行副作用（第一次不会直接执行）；</li><li>更具体的说明当哪些状态发生变化时，触发侦听器的执行；</li><li>访问侦听状态变化前后的值；</li></ul><p>watch 侦听函数的数据源有两种类型：</p><ul><li>一个 getter 函数：但是该 getter 函数必须引用可响应式的对象（比如 reactive 或者 ref）；</li><li>直接写入一个可响应式的对象，reactive 或者 ref（比较常用的是 ref）；</li></ul><p>watch</p><p>监听响应式对象</p><p>如果我们希望侦听一个数组或者对象，那么可以使用一个 getter 函数，并且对可响应对象进行解构：</p><div class="language-typescript"><pre><code><span class="token keyword">const</span> names <span class="token operator">=</span> <span class="token function">reactive</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">&quot;abc&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;cbd&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;efg&quot;</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">watch</span><span class="token punctuation">(</span>
  <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>names<span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">(</span>newValue<span class="token punctuation">,</span> oldValue<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>newValue<span class="token punctuation">,</span> oldValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    deep<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// ===&gt;  深层监听</span>
    immediate<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// ==&gt; 立即执行</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>​ effect 也有自己的参数 清除副作用（onInvalidate ）</p><p>​ onInvalidate 被调用的时机很微妙：它只作用于异步函数，并且只有在如下两种情况下才会被调用：</p><p>​ 1 当 effect 函数被重新调用时</p><p>​ 2 当监听器被注销时（如组件被卸载了）</p><p>​ 第一个参数 (effect) 用来收集 响应式元素()依赖收集时阶段会运行一次 effect 函数\</p><div class="language-typescript"><pre><code><span class="token function">watcheffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>
    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>count<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">{</span>
    flush<span class="token operator">:</span> <span class="token string">&#39;pre&#39;</span>  <span class="token comment">// &#39;post&#39;    &#39;sync&#39;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>


<span class="token comment">///////////   只在开发模式这样做</span>

<span class="token function">watcheffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>
    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">&quot;count.value&quot;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">{</span>
    <span class="token comment">//debugger</span>
    <span class="token function">onTrack</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">{</span>
    <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">&quot;我一开始就会触发&quot;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function">onTrigger</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">{</span>
        <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">&#39;我只有监听的依赖改变了,我才会触发&#39;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
           <span class="token punctuation">)</span>
</code></pre></div><div class="language-javascript"><pre><code> <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">const</span> count <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> <span class="token function-variable function">effect</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>count<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
       <span class="token function">watchEffect</span><span class="token punctuation">(</span>effect<span class="token punctuation">)</span><span class="token punctuation">;</span>
       <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> count<span class="token punctuation">.</span>value<span class="token operator">++</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	 <span class="token keyword">return</span> <span class="token punctuation">{</span> count <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
</code></pre></div><hr><p>源码</p><div class="language-typescript"><pre><code><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isRef</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function-variable function">getter</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">(</span>source <span class="token keyword">as</span> Ref<span class="token punctuation">)</span><span class="token punctuation">.</span>value<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    forceTrigger <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span><span class="token punctuation">(</span>source <span class="token keyword">as</span> Ref<span class="token punctuation">)</span><span class="token punctuation">.</span>_shallow
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isReactive</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function-variable function">getter</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> source
    deep <span class="token operator">=</span> <span class="token boolean">true</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isArray</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function-variable function">getter</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
      source<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>s <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isRef</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">return</span> s<span class="token punctuation">.</span>value
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isReactive</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">return</span> <span class="token function">traverse</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isFunction</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">return</span> <span class="token function">callWithErrorHandling</span><span class="token punctuation">(</span>s<span class="token punctuation">,</span> instance<span class="token punctuation">,</span> ErrorCodes<span class="token punctuation">.</span><span class="token constant">WATCH_GETTER</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>
            instance <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>instance<span class="token punctuation">.</span>proxy <span class="token keyword">as</span> <span class="token builtin">any</span><span class="token punctuation">)</span>
          <span class="token punctuation">]</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
          __DEV__ <span class="token operator">&amp;&amp;</span> <span class="token function">warnInvalidSource</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isFunction</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>cb<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// getter with cb</span>
      <span class="token function-variable function">getter</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
        <span class="token function">callWithErrorHandling</span><span class="token punctuation">(</span>source<span class="token punctuation">,</span> instance<span class="token punctuation">,</span> ErrorCodes<span class="token punctuation">.</span><span class="token constant">WATCH_GETTER</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>
          instance <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>instance<span class="token punctuation">.</span>proxy <span class="token keyword">as</span> <span class="token builtin">any</span><span class="token punctuation">)</span>
        <span class="token punctuation">]</span><span class="token punctuation">)</span>
</code></pre></div></div></div><footer class="page-footer" data-v-7eddb2c4 data-v-fb8d84c6><div class="edit" data-v-fb8d84c6><div class="edit-link" data-v-fb8d84c6 data-v-1ed99556><!----></div></div><div class="updated" data-v-fb8d84c6><!----></div></footer><!----><!--[--><!--]--></div></main></div><!----><!--]--></div>
    <script>__VP_HASH_MAP__ = JSON.parse("{\"index.md\":\"992e2bc4\",\"css_advanced.md\":\"157298f3\",\"css_index.md\":\"0df1ef28\",\"browser_advanced.md\":\"a7a79457\",\"browser_index.md\":\"9938c247\",\"browser_plugin.md\":\"fd27046d\",\"git_index.md\":\"fb33a92a\",\"html_advanced.md\":\"599ad3d5\",\"html_index.md\":\"c94736b6\",\"interview_index.md\":\"07dc8acc\",\"interview_two.md\":\"0ca812f6\",\"javascript_advanced.md\":\"5c434e6d\",\"javascript_index.md\":\"d996a3fc\",\"jenkins_index.md\":\"3068e232\",\"myself_index.md\":\"340fc837\",\"myself_one.md\":\"31202d59\",\"myself_pre.md\":\"aa93248e\",\"network_index.md\":\"7251e8b9\",\"network_security.md\":\"d11dae3c\",\"node_index.md\":\"e0b22fc7\",\"node_pit.md\":\"a1502c64\",\"npm_index.md\":\"59b033b6\",\"pit_index.md\":\"603d46aa\",\"solution_index.md\":\"987aa705\",\"typescript_advanced.md\":\"431b8bba\",\"typescript_combat.md\":\"60c9a8a2\",\"typescript_index.md\":\"bd88b5a2\",\"vite_index.md\":\"d6838c03\",\"vite_optimize.md\":\"49f1bb24\",\"vue_index.md\":\"380ec221\",\"vue_reactive.md\":\"206c8e46\",\"vue_router.md\":\"0d556c25\",\"vue_source.md\":\"592e192a\",\"vue_vuex.md\":\"032502ef\",\"webpack_advanced.md\":\"ae6c63b7\",\"webpack_common.md\":\"141deb7a\",\"webpack_custom.md\":\"4827ebf8\",\"webpack_index.md\":\"49f185a6\",\"webpack_optimize.md\":\"545698f9\",\"vue_source_index.md\":\"1c12bc29\",\"vue_source_nexttick.md\":\"4533a28b\",\"vue_source_watch.md\":\"0e7342fc\"}")</script>
    <script type="module" async src="/interview/assets/app.49accf37.js"></script>
  </body>
</html>